#include "pythonXFunctions.h"

#include <iostream>
#include <stdexcept>
#include <string>
#include <Python.h>
#include "bindings.h"
#include "utils.h"
#include "pythonObjects.h"

namespace s2e {
  namespace plugins {
    namespace callbacks {

      void after(S2EExecutionState *state, s2e::plugins::Instruction* inst) {
        /* Check if there is a callback wich must be called at each instruction instrumented */
        if (s2e::plugins::options::callbackAfter) {

          PyObject* stateClass = s2e::plugins::python::PyS2EState(state);
          /* Create the Instruction Python class */
          PyObject* instClass = s2e::plugins::python::PyInstruction(*inst);

          /* CallObject needs a tuple. The size of the tuple is the number of arguments.
           * Triton sends only one argument to the callback. This argument is the Instruction
           * class and contains all information. */

          PyObject* args = s2e::plugins::python::xPyTuple_New(2);
	      PyTuple_SetItem(args, 0, stateClass);
		  PyTuple_SetItem(args, 1, instClass);
          if (PyObject_CallObject(s2e::plugins::options::callbackAfter, args) == nullptr) {
            PyErr_Print();
            exit(1);
          }

          Py_DECREF(args);
        }
      }


      void before(S2EExecutionState *state, s2e::plugins::Instruction* inst) {
        /* Check if there is a callback wich must be called at each instruction instrumented */
        if (s2e::plugins::options::callbackBefore){

            PyObject* stateClass = s2e::plugins::python::PyS2EState(state);
            /* Create the Instruction Python class */
            PyObject* instClass = s2e::plugins::python::PyInstruction(*inst);

            /* CallObject needs a tuple. The size of the tuple is the number of arguments.
             * Triton sends only one argument to the callback. This argument is the Instruction
             * class and contains all information. */

            PyObject* args = s2e::plugins::python::xPyTuple_New(2);
  	        PyTuple_SetItem(args, 0, stateClass);
  		    PyTuple_SetItem(args, 1, instClass);
          if (PyObject_CallObject(s2e::plugins::options::callbackBefore, args) == nullptr) {
            PyErr_Print();
            exit(1);
          }

          Py_DECREF(args);
        }
      }


      void fini(S2EExecutionState *state) {
        /* Check if there is a callback wich must be called at the end of the execution */
        if (s2e::plugins::options::callbackFini) {
        	PyObject* stateClass = s2e::plugins::python::PyS2EState(state);
          /* CallObject needs a tuple. The size of the tuple is the number of arguments.
           * There is no argument sent to the callback. */
          PyObject* args = s2e::plugins::python::xPyTuple_New(1);
          PyTuple_SetItem(args, 0, stateClass);
          if (PyObject_CallObject(s2e::plugins::options::callbackFini, args) == nullptr) {
            PyErr_Print();
            exit(1);
          }

          Py_DECREF(args);
        }
      }

      void syscallEntry(S2EExecutionState *state, s2e::uint64 num) {
        /* Check if there is a callback wich must be called before the syscall processing */
        if (s2e::plugins::options::callbackSyscallEntry) {
        	PyObject* stateClass = s2e::plugins::python::PyS2EState(state);
          /* CallObject needs a tuple. The size of the tuple is the number of arguments.
           * threadId and Std are sent to the callback. */
          PyObject* args = s2e::plugins::python::xPyTuple_New(2);
          PyTuple_SetItem(args, 0, stateClass);
          PyTuple_SetItem(args, 1, s2e::plugins::python::PyLong_FromUint32(num));
          if (PyObject_CallObject(s2e::plugins::options::callbackSyscallEntry, args) == nullptr) {
            PyErr_Print();
            exit(1);
          }
          Py_DECREF(args);
        }
      }


      void syscallExit(S2EExecutionState *state, s2e::uint64 num) {
        /* Check if there is a callback wich must be called after the syscall processing */
        if (s2e::plugins::options::callbackSyscallExit) {
        	PyObject* stateClass = s2e::plugins::python::PyS2EState(state);
          /* CallObject needs a tuple. The size of the tuple is the number of arguments.
           * threadId and Std are sent to the callback. */
          PyObject* args = s2e::plugins::python::xPyTuple_New(2);
          PyTuple_SetItem(args, 0, stateClass);
          PyTuple_SetItem(args, 1, s2e::plugins::python::PyLong_FromUint32(num));
          if (PyObject_CallObject(s2e::plugins::options::callbackSyscallExit, args) == nullptr) {
            PyErr_Print();
            exit(1);
          }
          Py_DECREF(args);
        }
      }
    };
  };
};

